Skip to content

Conversation

@henriquemoody
Copy link
Member

While basic template interpolation is useful, we know that a one-size-fits-all stringification approach isn't always desirable. Sometimes you need to bypass the standard formatting logic or transform a value before it hits the final string. This commit introduces a flexible modifier system to solve that.

I’ve adopted the familiar {{value|modifier}} syntax found in many popular template engines. To handle this transformation, I implemented a Chain of Responsibility pattern. This allows us to pipe values through specific logic, such as the new |raw modifier, which provides clean scalar output by bypassing the default stringifier formatting.

The StringifyModifier remains the default fallback, ensuring that we don't lose the robust type-handling we previously built. By separating these transformations into an extensible modifier interface, we’ve made the system future-proof—users can now plug in custom transformations without touching the core formatting logic.

This change turns our simple placeholder replacement into a proper interpolation engine, giving developers fine-grained control over how their data is presented.

Assisted-by: OpenCode (GLM-4.6)

@codecov-commenter
Copy link

codecov-commenter commented Jan 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.23%. Comparing base (fb2db2d) to head (ce93a1d).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main       #2      +/-   ##
============================================
+ Coverage     99.04%   99.23%   +0.18%     
- Complexity       97      128      +31     
============================================
  Files             4       10       +6     
  Lines           210      261      +51     
============================================
+ Hits            208      259      +51     
  Misses            2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a modifier system to PlaceholderFormatter, enabling fine-grained control over value transformations through a {{value|modifier}} syntax. The implementation uses a Chain of Responsibility pattern where modifiers can either handle transformations or pass values to the next modifier in the chain.

Changes:

  • Introduced Modifier interface and two implementations: RawModifier (for clean scalar output) and StringifyModifier (default fallback)
  • Updated PlaceholderFormatter to parse and apply modifiers using regex pattern /{{(\w+)(\|([^}]+))?}}/
  • Added comprehensive documentation for modifiers including usage examples and custom modifier creation guide

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/Modifier.php New interface defining the modifier contract with modify() method
src/Modifier/RawModifier.php Implements raw modifier for scalar values, bypassing stringifier formatting
src/Modifier/StringifyModifier.php Wrapper modifier delegating to stringifier for value conversion
src/PlaceholderFormatter.php Updated to parse modifier syntax and apply modifier chain instead of direct stringifier
tests/Unit/PlaceholderFormatterTest.php Updated test to verify custom modifier support
tests/Unit/Formatter/RawModifierTest.php Comprehensive test suite for RawModifier behavior
tests/Helper/TestingModifier.php Test helper implementing Modifier interface for testing
docs/PlaceholderFormatter.md Updated with modifier documentation and corrected examples
docs/modifiers/Modifiers.md New overview documentation for modifier system
docs/modifiers/RawModifier.md Detailed documentation for RawModifier
docs/modifiers/StringifyModifier.md Detailed documentation for StringifyModifier
docs/modifiers/CreatingCustomModifiers.md Guide for implementing custom modifiers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@henriquemoody
Copy link
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

@henriquemoody henriquemoody marked this pull request as draft January 19, 2026 15:45
@henriquemoody henriquemoody force-pushed the modifiers branch 2 times, most recently from bb58a9f to 7641c33 Compare January 19, 2026 18:04
@henriquemoody henriquemoody requested a review from Copilot January 19, 2026 18:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@henriquemoody
Copy link
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 16 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Although basic interpolation is useful, we often want to handle parameters
differently, not using the standard modification we apply to them. This commit
introduces modifiers, a concept already present in Respect\Validation.

We use a familiar `{{value|modifier}}` syntax found in many popular template
engines. To handle different types of modifiers, I implemented a Chain of
Responsibility, where each modifier delegates to the next one when it cannot
handle them.

The `StringifyModifier` remains the default fallback, ensuring that we don't
lose the robust type-handling we previously built, but I've also made some
changes to it. I realise that for Respect\Validation, we have a strong need to
add "quotes" around strings because they represent values that were just
validated, but in the context of placeholder replacement, this behaviour is
undesirable.

I've created the `AutoQuoteModifier`, which quotes scalar values by default and
provides a `|raw` pipe to bypass quoting when clean output is needed. This
allows Validation to retain its quoting behaviour while keeping the default
`PlaceholderFormatter` chain simple.

Assisted-by: OpenCode (GLM-4.6)
Assisted-by: Gemini 3 (Thinking)
I based this new modifier on the `ListAndModifier` and `ListOrModifier` from
Respect\Validation, but I wanted to improve on that design. Instead of
maintaining two separate modifiers that repeat almost identical behavior
just to change a conjunction, I’ve unified them into a single, versatile
`ListModifier`.

The power of this new approach lies in its flexibility; it allows for
configurable conjunctions directly via the pipe syntax. This allows us to
transform raw arrays into lists that actually make sense to a human reader.
Whether we need a list joined by "and," "or," we can now produce more natural
strings instead of falling back on a simple comma-separated list.

Assisted-by: OpenCode (GLM-4.6)
Assisted-by: Gemini 3 (Thinking)
I’ve ported the `QuoteModifier` from Respect\Validation, but I’ve taken a
different approach for this implementation. While the original version relied on
and implementation of `Quoter` (from Respect\Stringifier) dependency, I decided
to remove it for this context.

The `Quoter` interface system was designed to handle complex stringification
levels and deep nesting—functionality that is simply overkill for this modifier.
By stripping that dependency away, we’ve eliminated unnecessary complexity.

A major benefit of this simplification is increased flexibility. Instead of
being locked into a predefined quoting strategy, users can now define their
preferred quoting character (such as ', ", or backticks) directly in the
constructor.

Assisted-by: OpenCode (GLM-4.6)
Assisted-by: Gemini 3 (Thinking)
Another need we had in Respect\Validation was the ability to translate
parameters into a different language, and I wanted to port that functionality to
this library as well. I’ve ported the `TransModifier` from Validation, allowing
us to bridge the gap between template interpolation and localization.

Instead of reinventing the wheel, I’ve integrated this modifier with
`symfony/translation`. By relying on the TranslatorInterface, we ensure robust
translation ecosystems without being locked into a specific implementation.

Assisted-by: OpenCode (GLM-4.6)
Assisted-by: Gemini 3 (Thinking)
@henriquemoody henriquemoody merged commit ce93a1d into Respect:main Jan 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants